using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Xwing
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region Properties
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        BasicEffect basicEffect;

        Matrix skyBoxMatrix;                    //SkyBox properties
        Model skyBoxModel;
        Vector3 SkyBoxPosition, Zoom;
        float SkyBoxSize, Speed;

        Matrix xWingMatrix;                     //Xwing properties
        Model xWingModel;
        Vector3 xWingPosition;
        float XWingSize;

        Matrix deathStarMatrix;                //DeathStar properties
        Model deathStarModel;
        Vector3 deathStarPosition;
        float deathStarSize;

        Matrix meteroiteMatrix;                //Meteorite properties
        List<Matrix> collisionPosList;
        Model meteroiteModel;
        Vector3 meteroitePosition, meteroiteRotation;
        int numberOfMeteorites;
        List<Meteorite> meteorites;

        Matrix viewMatrix;                      //Camera properties
        Matrix projectionMatrix;
        Matrix viewMatrix1;
        Matrix projectionMatrix1;
        Matrix FollowCameraPositionMatrix;
        Matrix TopCameraPositionMatrix;
        Viewport defaultViewport;
        Viewport OverView;

        BoundingSphere deathStarSphere, spaceSphere;    //Collision properties
        List<BoundingSphere> meteroriteSpheres;
        enum CollisionType { None, Deathstar, Boundary, Asteroid }
        
        float aspectRatio, timer;                //Other properties
        Random random;

        Song song;
        SpriteFont font;
        Vector2 FontPosition;
        string text;
        bool endGameState, restartPress;
        int type;

        #endregion

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        #region Initializing function
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()                        //Load mostly graphics options
        {
            random = new Random();
            endGameState = false;
            meteroriteSpheres = new List<BoundingSphere>();
            collisionPosList = new List<Matrix>();
            meteorites = new List<Meteorite>();
            spriteBatch = new SpriteBatch(GraphicsDevice);
            basicEffect = new BasicEffect(GraphicsDevice);

            graphics.IsFullScreen = false;
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 768;
            graphics.ApplyChanges();

            defaultViewport = GraphicsDevice.Viewport;
            OverView = defaultViewport;
            OverView.Width = OverView.Width / 4;
            OverView.Height = OverView.Height / 4;
            OverView.X = defaultViewport.Width - OverView.Width;
            OverView.Y = defaultViewport.Height - OverView.Height;

            aspectRatio =                                               //View Settings
                ((float)defaultViewport.Width) /
                ((float)defaultViewport.Height);

            viewMatrix = Matrix.Identity;
            projectionMatrix =
                Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(90.0f),
                aspectRatio, 1.0f, 100000.0f);

            viewMatrix1 = Matrix.Identity;
            projectionMatrix1 =
                Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(90.0f),
                aspectRatio, 100.0f, 100000.0f);

            FollowCameraPositionMatrix = Matrix.Identity;
            TopCameraPositionMatrix = Matrix.Identity;

            song = Content.Load<Song>("epv1");                      //Music!

            font = Content.Load<SpriteFont>("Font");             //Font
            FontPosition = new Vector2(
                defaultViewport.Width - (OverView.Width / 2),
                defaultViewport.Height - (OverView.Height));

            restartPress = false;
            type = 4;

            InitializeGame();
        }

        private void InitializeGame()                            //Mostly things that needs to be reinitlalized
        {
            skyBoxModel = Content.Load<Model>("SkySphereFantasy");      //Skybox
            skyBoxMatrix = Matrix.Identity;
            SkyBoxPosition = new Vector3(0.0f, 0.0f, 0.0f);
            SkyBoxSize = 1000.0f;

            xWingModel = Content.Load<Model>("xwing");                          //xwing
            xWingMatrix = Matrix.Identity
                * Matrix.CreateTranslation(new Vector3(10000.0f, 10000.0f, 10000.0f));
            xWingPosition = new Vector3(0.0f, 0.0f, 0.0f);
            Zoom = new Vector3(0.0f, 30.0f, 60.0f);
            XWingSize = 0.1f;
            Speed = 100.0f;

            deathStarModel = Content.Load<Model>("DeathStar");                  //DeathStar
            deathStarMatrix = Matrix.Identity;
            deathStarPosition = new Vector3(0.0f, 0.0f, 0.0f);
            deathStarSize = 1000.0f;

            meteorites.Clear();                                             //Meteorites
            collisionPosList.Clear();
            Components.Clear();
            meteroriteSpheres.Clear();
            meteroiteModel = Content.Load<Model>("Meteroite");
            meteroiteMatrix = Matrix.Identity;
            meteroitePosition = new Vector3(0.0f, 0.0f, 0.0f);
            meteroiteRotation = new Vector3(0.0f, 0.0f, 0.0f);
            numberOfMeteorites = 100;

            for (int i = 0; i < numberOfMeteorites; i++)
            {
                Matrix temp = Matrix.Identity;
                meteorites.Add(AddMeteorite());
                collisionPosList.Add(temp);
                Components.Add(meteorites[i]);
            }

            timer = 0.0f;                           //timer

            SetUpBoundingSpheres();                 //Setup BoundingSpheres which are used for collision detection

            base.Initialize();
        } 
        #endregion

        //never used
        #region Load/UnloadContent
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        } 
        #endregion

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            random = new Random();
            controlls();

            if (!endGameState)
            {
                timerFunc(gameTime);

                if (timer > 2.0f && type != 4)
                    type = 4;

                FollowCamera();
                OverviewCameraSet();
                UpdateBoundingSpheres();
                ifCollisionXWing();

            }
            base.Update(gameTime);
        }

        #region Xwing Controlls
        private void controlls()
        {
            xWingMatrix.Translation += xWingMatrix.Forward * Speed;
            KeyboardState kbstate = Keyboard.GetState();

            if (kbstate.IsKeyDown(Keys.LeftShift))
                xWingMatrix.Translation -= xWingMatrix.Forward * Speed;
            if (kbstate.IsKeyDown(Keys.M))
                MediaPlayer.Play(song);
            if (kbstate.IsKeyDown(Keys.N))
                MediaPlayer.Pause();

            if (kbstate.IsKeyDown(Keys.Escape))
                this.Exit();

            if (kbstate.IsKeyDown(Keys.Space) && restartPress == true)
            {
                endGameState = false;
                restartPress = false;
                InitializeGame();
            }

            if (kbstate.IsKeyDown(Keys.Left))
                xWingSteering(-0.05f, 0.0f, -0.0f);

            if (kbstate.IsKeyDown(Keys.Right))
                xWingSteering(0.05f, 0.0f, 0.0f);

            if (kbstate.IsKeyDown(Keys.Up))
                xWingSteering(0.0f, -0.05f, 0.0f);

            if (kbstate.IsKeyDown(Keys.Down))
                xWingSteering(0.0f, 0.05f, 0.0f);

            if (kbstate.IsKeyDown(Keys.Z))
                xWingSteering(0.0f, 0.0f, -0.05f);

            if (kbstate.IsKeyDown(Keys.X))
                xWingSteering(0.0f, 0.0f, 0.05f);
        }

        private void xWingSteering(float xPitch, float yYaw, float zRoll)
        {
            xWingMatrix = xWingMatrix
                * Matrix.CreateTranslation(-xWingMatrix.Translation.X, -xWingMatrix.Translation.Y, -xWingMatrix.Translation.Z)
                * Matrix.CreateFromAxisAngle(xWingMatrix.Down, xPitch)
                * Matrix.CreateFromAxisAngle(xWingMatrix.Right, yYaw)
                * Matrix.CreateFromAxisAngle(xWingMatrix.Forward, zRoll)
                * Matrix.CreateTranslation(xWingMatrix.Translation.X, xWingMatrix.Translation.Y, xWingMatrix.Translation.Z);
        } 
        #endregion

        #region CameraViews
        private void FollowCamera()
        {
            FollowCameraPositionMatrix = xWingMatrix;
            viewMatrix = Matrix.CreateLookAt(
                FollowCameraPositionMatrix.Translation + xWingMatrix.Up * Zoom.Y + xWingMatrix.Backward * Zoom.Z,
                xWingMatrix.Translation, xWingMatrix.Up);
        }

        private void OverviewCameraSet()
        {
            TopCameraPositionMatrix = xWingMatrix                   //Overview Camera Settings
                * Matrix.CreateTranslation(0.0f, 400.0f, 0.0f);
            viewMatrix1 = Matrix.CreateLookAt(
                TopCameraPositionMatrix.Translation,
                xWingMatrix.Translation, Vector3.Forward);
        } 
        #endregion

        #region Draw
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            spriteBatch.Begin();
            GraphicsDevice.DepthStencilState = new DepthStencilState() { DepthBufferEnable = true };
            Matrix viewPortMatrix, projectionPortMatrix;
            GraphicsDevice.Clear(Color.Black);
           
                for (int i = 0; i < 2; i++)
                {
                    if (i == 0)
                    {
                        GraphicsDevice.Viewport = defaultViewport;
                        viewPortMatrix = viewMatrix;
                        projectionPortMatrix = projectionMatrix;
                    }
                    else
                    {
                        GraphicsDevice.Viewport = OverView;
                        viewPortMatrix = viewMatrix1;
                        projectionPortMatrix = projectionMatrix1;
                    }

                    DrawModel(deathStarModel, deathStarMatrix, viewPortMatrix, projectionPortMatrix, RasterizerState.CullCounterClockwise, deathStarPosition, deathStarSize, 0.0f, 180.0f, 0.0f, 0);
                    DrawModel(skyBoxModel, skyBoxMatrix, viewPortMatrix, projectionPortMatrix, RasterizerState.CullCounterClockwise, SkyBoxPosition, SkyBoxSize, 0.0f, -90.0f, 0.0f, 0);
                    DrawModel(xWingModel, xWingMatrix, viewPortMatrix, projectionPortMatrix, RasterizerState.CullNone, xWingPosition, XWingSize, 0.0f, 180.0f, 0.0f, 0);
                    for (int j = 0; j < numberOfMeteorites; j++)
                    {
                        DrawModel(meteorites[j].model, meteorites[j].matrix, viewPortMatrix, projectionPortMatrix, RasterizerState.CullCounterClockwise, meteorites[j].position, meteorites[j].size, meteorites[j].rotation.X, meteorites[j].rotation.Y, meteorites[j].rotation.Z, j);
                    }
                }

            GraphicsDevice.Viewport = defaultViewport;

            spriteBatch.DrawString(font, timer.ToString(), new Vector2(defaultViewport.Width-200, defaultViewport.Height-50), Color.Gold, 0.0f, new Vector2(0.0f, 0.0f), 0.5f, SpriteEffects.None, 0.0f);

            printEndGame(type);

            spriteBatch.End();
            base.Draw(gameTime);
        }

        private void DrawModel(Model m, Matrix world, Matrix view, Matrix projection, RasterizerState Cull, Vector3 position, float size, float Xrot, float Yrot, float Zrot, int MeteoriteListIndex)
        {
            GraphicsDevice.RasterizerState = Cull;
            Matrix[] transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in m.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.View = view;
                    effect.Projection = projection;
                    effect.World = transforms[mesh.ParentBone.Index]
                        * Matrix.CreateRotationX(MathHelper.ToRadians(Xrot))
                        * Matrix.CreateRotationY(MathHelper.ToRadians(Yrot))
                        * Matrix.CreateRotationZ(MathHelper.ToRadians(Zrot))
                        * Matrix.CreateTranslation(position)
                        * Matrix.CreateScale(size)
                        * world;

                    if (m == meteroiteModel)
                    {
                        collisionPosList.RemoveAt(MeteoriteListIndex);
                        collisionPosList.Insert(MeteoriteListIndex, effect.World);
                    }
                }
                mesh.Draw();
            }
        }

        private void printEndGame(int type)
        {
            if (type == 0)
            {
                text = "You win!";
                Vector2 textSize = font.MeasureString(text);
                Vector2 textCenter = new Vector2(GraphicsDevice.Viewport.Width / 2, 100f);
                spriteBatch.DrawString(font, text, textCenter - (textSize / 2), Color.Gold);
            }
            else if (type == 1)
            {
                text = "You've left the \nbattle grounds!";
                Vector2 textSize = font.MeasureString(text);
                Vector2 textCenter = new Vector2(GraphicsDevice.Viewport.Width / 2, 100f);
                spriteBatch.DrawString(font, text, textCenter - (textSize / 2), Color.Gold);
            }
            else if (type == 2)
            {
                text = "Killed in action!";
                Vector2 textSize = font.MeasureString(text);
                Vector2 textCenter = new Vector2(GraphicsDevice.Viewport.Width / 2, 100f);
                spriteBatch.DrawString(font, text, textCenter - (textSize / 2), Color.Gold);
            }
        }
        #endregion

        private void timerFunc(GameTime gameTime)
        {
            timer += (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        private Meteorite AddMeteorite()
        {
            int state = random.Next(3);
            int axis = random.Next(3);

            float x = (float)random.Next(-200, 200) / 25;
            float y = (float)random.Next(-200, 200) / 25;
            float z = (float)random.Next(-200, 200) / 25;

            while ((x > -0.5f && x < 0.5f) || (y > -0.5f && y < 0.5f) || (z > -0.5f && z < 0.5f))
            {
                x = (float)random.Next(-200, 200) / 25;
                y = (float)random.Next(-200, 200) / 25;
                z = (float)random.Next(-200, 200) / 25 ;
            }

            float xrot = (float)random.Next(200) / 100;
            float yrot = (float)random.Next(200) / 100;
            float zrot = (float)random.Next(200) / 100;

            Vector3 position = new Vector3(x, y, z);
            Vector3 rotation = new Vector3(xrot, yrot, zrot);
            float Size = random.Next(700, 1500);

            Meteorite newMeteorite = new Meteorite(this, meteroiteModel, meteroiteMatrix, position, rotation, state, Size, axis);
            return newMeteorite;
        }

        #region Collision functions
        private void ifCollisionXWing()
        {
            BoundingSphere xwingSphere = new BoundingSphere(xWingMatrix.Translation, XWingSize * 1000);
            if (CheckCollision(xwingSphere) == CollisionType.Deathstar)
            {                                                                 //If collision with deathstar
                restartPress = true;
                type = 0;
                endGameState = !endGameState;
            }

            if (CheckCollision(xwingSphere) == CollisionType.Asteroid)
            {                                                                 //If collision with astroid
                type = 2;
                InitializeGame();
            }

            if (CheckCollision(xwingSphere) == CollisionType.Boundary)
            {                                                                 //If collision with skybox
                type = 1;
                InitializeGame();
            }
        }

        private void SetUpBoundingSpheres()
        {
            deathStarSphere = new BoundingSphere(deathStarMatrix.Translation, deathStarSize * 111 / 100);
            spaceSphere = new BoundingSphere(skyBoxMatrix.Translation, SkyBoxSize * 20);

            for (int i = 0; i < numberOfMeteorites; i++)                            //Alot of meteorites
            {
                meteroriteSpheres.Add(new BoundingSphere(collisionPosList[i].Translation, meteorites[i].size));
            }
        }

        private void UpdateBoundingSpheres()                            //The meteorites are moving objects
        {                                                               //So their boundingboxes need to be updated alot.
            for (int i = 0; i < numberOfMeteorites; i++)
            {
                meteroriteSpheres.RemoveAt(i);
                meteroriteSpheres.Insert(i, new BoundingSphere(collisionPosList[i].Translation, meteorites[i].size));
            }
        }

        private CollisionType CheckCollision(BoundingSphere sphere)                     //Collision function
        {
            if (deathStarSphere.Contains(sphere) == ContainmentType.Contains)
            {
                return CollisionType.Deathstar;
            }

            for (int i = 0; i < meteroriteSpheres.Count; i++)
                if (meteroriteSpheres[i].Contains(sphere) == ContainmentType.Intersects)
                    return CollisionType.Asteroid;

            if (spaceSphere.Contains(sphere) != ContainmentType.Contains)
                return CollisionType.Boundary;

            return CollisionType.None;
        } 
        #endregion
    }
}
